#include <GL/glew.h>
#include <GL/freeglut.h>
#include <glm/ext.hpp>
#include <iostream>
#include <vector>
using namespace std;

#include "technique.h"

//shader program for render cutted mesh
MeshCutTechnique gCutTechnique;
//shader program for render cut plane
SimpleTechnique gSimpTechnique;
//camera pos for rendering
glm::vec3 cameraPos(0, 0, -4);
glm::vec3 cameraTarget(0, 0, 0);
glm::vec3 cameraUp(0, 1, 0);
//cut plane equation: Ax+By+Cz+D=0 <=> plane(A,B,C,D)
glm::vec4 plane(0, 1, 0, -0.5);

struct Vertex{
	glm::vec3 coord;
	glm::vec3 normal;
};

GLuint vao, vbo, ibo;
GLuint vao1, vbo1, ibo1;

//create a simple mesh for rendering
bool LoadMesh()
{
	const float v = 1.0 / sqrt(3);
	std::vector<Vertex> uVertex = { 
	{ glm::vec3(0, 0, 0), glm::vec3(0, 0, -1) },
	{ glm::vec3(0, 1, 0), glm::vec3(0, 0, -1) },
	{ glm::vec3(1, 0, 0), glm::vec3(0, 0, -1) },

	{ glm::vec3(0, 0, 0), glm::vec3(-1, 0, 0) },
	{ glm::vec3(0, 0, 1), glm::vec3(-1, 0, 0) },
	{ glm::vec3(0, 1, 0), glm::vec3(-1, 0, 0) },

	{ glm::vec3(0, 0, 0), glm::vec3(0, -1, 0) },
	{ glm::vec3(1, 0, 0), glm::vec3(0, -1, 0) },
	{ glm::vec3(0, 0, 1), glm::vec3(0, -1, 0) },

	{ glm::vec3(1, 0, 0), glm::vec3(v, v, v) },
	{ glm::vec3(0, 1, 0), glm::vec3(v, v, v) },
	{ glm::vec3(0, 0, 1), glm::vec3(v, v, v) },
	};

	std::vector<unsigned int> uFace = {
		0, 1, 2,
		3, 4, 5,
		6, 7, 8,
		9, 10, 11
	};

	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*uVertex.size(), &uVertex[0], GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const void*)(sizeof(glm::vec3)));
	glGenBuffers(1, &ibo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int)*uFace.size(), &uFace[0], GL_STATIC_DRAW);
	glBindVertexArray(0);

	return true;
}

//create a plane for cut mesh and rendering
bool LoadPlane()
{
	std::vector<Vertex> uVertex = {
		{ glm::vec3(-10, 0.5, -10), glm::vec3(0, -1, 0) },
		{ glm::vec3(10, 0.5, -10), glm::vec3(0, -1, 0) },
		{ glm::vec3(10, 0.5, 10), glm::vec3(0, -1, 0) },

		{ glm::vec3(-10, 0.5, -10), glm::vec3(0, -1, 0) },
		{ glm::vec3(10, 0.5, 10), glm::vec3(0, -1, 0) },
		{ glm::vec3(-10, 0.5, 10), glm::vec3(0, -1, 0) },
	};

	std::vector<unsigned int> uFace = {
		0, 1, 2,
		3, 4, 5,
	};

	glGenVertexArrays(1, &vao1);
	glBindVertexArray(vao1);
	glGenBuffers(1, &vbo1);
	glBindBuffer(GL_ARRAY_BUFFER, vbo1);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*uVertex.size(), &uVertex[0], GL_STATIC_DRAW);
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const void*)(sizeof(glm::vec3)));
	glGenBuffers(1, &ibo1);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo1);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int)*uFace.size(), &uFace[0], GL_STATIC_DRAW);
	glBindVertexArray(0);

	return true;
}

void specialkey(int key, int x, int y)
{
	if (key == GLUT_KEY_UP)
	{
		plane.w -= 0.1;
		glutPostRedisplay();
	}
	else if (key == GLUT_KEY_DOWN)
	{
		plane.w += 0.1;
		glutPostRedisplay();
	}
}

void display()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	const float pi = acos(-1.0);
	glm::mat4 viewMat = glm::lookAt(cameraPos, cameraTarget, cameraUp);
	glm::mat4 projMat = glm::perspective(60.0/180.0*pi, 1.0, 1.0, 1000.0);
	glm::mat4 wvp = projMat*viewMat;
	gCutTechnique.Enable();
	gCutTechnique.setWorld(glm::mat4(1));
	gCutTechnique.setWVP(wvp);
	gCutTechnique.setClipPlane(plane);
	glEnable(GL_CLIP_PLANE0);//enable plane clip for mesh
	glStencilFunc(GL_ALWAYS, 0, 0);//set always pass stencil test when rendering mesh
	glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_ZERO);//set stencil buffer zero when redering front faces
	glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INCR_WRAP);//increase stencil buffer when rendering back faces
	glBindVertexArray(vao);
	glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);//rendering mesh

	glDisable(GL_CLIP_PLANE0);//disable plane clip, because now we will rendering plane
	gSimpTechnique.Enable();
	gSimpTechnique.setWorld(glm::mat4(1));
	gSimpTechnique.setWVP(wvp);

	glStencilFunc(GL_NOTEQUAL, 0, -1);//set pass stencil test when stencil buffer is not zero, whin means we can see back faces
	glStencilMask(0);//forbid the stencil buffer to be modified
	glBindVertexArray(vao1);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//draw plane
	glStencilMask(-1);

	glutSwapBuffers();
}

int main(int argc, char* argv[])
{
	glutInit(&argc, argv);
	glutInitContextVersion(4, 3);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL | GLUT_DOUBLE);
	glutInitWindowPosition(0, 0);
	glutInitWindowSize(600, 600);
	glutCreateWindow("MeshCutRender");
	auto error = glewInit();
	if (GLEW_OK != error)
	{
		cerr << "glew init error: " << glewGetErrorString(error) << endl;
		return 1;
	}

	glClearColor(1, 1, 1, 1);
	glClearDepth(1.0);
	glClearStencil(0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_STENCIL_TEST);//important: for rendering plane only on mesh


	if (!gCutTechnique.Init()){
		cerr << "shader init error" << endl;
		return 3;
	}
	gCutTechnique.Enable();
	gCutTechnique.setColor(glm::vec3(1, 0, 0));

	if (!gSimpTechnique.Init()){
		cerr << "shader init error" << endl;
		return 4;
	}
	gSimpTechnique.Enable();
	gSimpTechnique.setColor(glm::vec3(0, 1, 0));

	if (!LoadMesh())
	{
		cerr << "load mesh failed" << endl;
		return 2;
	}
	if (!LoadPlane())
	{
		cerr << "load plane failed" << endl;
		return 2;
	}

	glutDisplayFunc(display);
	glutSpecialFunc(specialkey);
	glutMainLoop();

	return 0;
}